﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SmartMathLibrary.NonlinearEquationSolvers
{
    /// <summary>
    /// This class provides the finding of roots of a function by using the Regula Falsi method.
    /// </summary>
    [Serializable]
    public class RegulaFalsiRootFinder : AbstractDerivativeNoNeedRootFinder
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="RegulaFalsiRootFinder"/> class.
        /// </summary>
        /// <param name="polynomial">The polynomial for finding the roots.</param>
        public RegulaFalsiRootFinder(Polynomial polynomial)
            : base(polynomial)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="RegulaFalsiRootFinder"/> class.
        /// </summary>
        /// <param name="polynomial">The polynomial for finding the roots.</param>
        public RegulaFalsiRootFinder(SimplePolynomial polynomial)
            : base(polynomial)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="RegulaFalsiRootFinder"/> class.
        /// </summary>
        /// <param name="function">The function for finding the roots.</param>
        public RegulaFalsiRootFinder(HardRealFunction function)
            : base(function)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="RegulaFalsiRootFinder"/> class.
        /// </summary>
        /// <param name="function">The function for finding the roots.</param>
        public RegulaFalsiRootFinder(IRealFunction function)
            : base(function)
        {
        }

        /// <summary>
        /// Finds one root of the function by using the Regula Falsi method. If the function
        /// has not real roots, which are between the specified values s and t, the result of the
        /// method is wrong. So the result of the method has to checked.
        /// </summary>
        /// <param name="s">The x value for the first starting point. It has to be a value on the left
        /// side of the root to find.</param>
        /// <param name="t">The x value for the second starting point. It has to be a value on the right
        /// side of the root to find.</param>
        /// <returns>
        /// A root of the function, which lies between the specified values s and t.
        /// </returns>
        public double FindRoots(double s, double t)
        {
            return this.FindRoots(s, t, 1e-15, 1000);
        }

        /// <summary>
        /// Finds one root of the function by using the Regula Falsi method. If the function
        /// has not real roots, which are between the specified values s and t, the result of the
        /// method is wrong. So the result of the method has to checked.
        /// </summary>
        /// <param name="s">The x value for the first starting point. It has to be a value on the left
        /// side of the root to find.</param>
        /// <param name="t">The x value for the second starting point. It has to be a value on the right
        /// side of the root to find.</param>
        /// <param name="iterations">The maximum of iterations to do, for finding the root.</param>
        /// <returns>
        /// A root of the function, which lies between the specified values s and t.
        /// </returns>
        public double FindRoots(double s, double t, int iterations)
        {
            return this.FindRoots(s, t, 1e-15, iterations);
        }

        /// <summary>
        /// Finds one root of the function by using the Regula Falsi method. If the function 
        /// has not real roots, which are between the specified values s and t, the result of the 
        /// method is wrong. So the result of the method has to checked.
        /// </summary>
        /// <param name="s">The x value for the first starting point. It has to be a value on the left 
        /// side of the root to find.</param>
        /// <param name="t">The x value for the second starting point. It has to be a value on the right 
        /// side of the root to find.</param>
        /// <param name="precision">The precision of the result.</param>
        /// <param name="iterations">The maximum of iterations to do, for finding the root.</param>
        /// <returns>A root of the function, which lies between the specified values s and t.</returns>
        public double FindRoots(double s, double t, double precision, int iterations)
        {
            int side = 0;
            double r = 0;
            double fr = 0;
            double fs = this.Function.SolveAt(s);
            double ft = this.Function.SolveAt(t);

            for (int n = 1; n <= iterations; n++)
            {
                r = (fs * t - ft * s) / (fs - ft);

                if (ExtendedMath.Fabs(t - s) < precision * ExtendedMath.Fabs(t + s))
                {
                    this.PrecisionError = false;
                    this.NeededIterations = n;
                    this.RelativeError = Math.Abs(ExtendedMath.Fabs(t - s));

                    return r;
                }

                fr = this.Function.SolveAt(r);

                if (fr * ft > 0)
                {
                    t = r;
                    ft = fr;

                    if (side == -1)
                    {
                        fs /= 2;
                    }

                    side = -1;
                }
                else if (fs * fr > 0)
                {
                    s = r;
                    fs = fr;

                    if (side == 1)
                    {
                        ft /= 2;
                    }

                    side = +1;
                }
                else
                {
                    this.PrecisionError = false;
                    this.NeededIterations = n;
                    this.RelativeError = Math.Abs(ExtendedMath.Fabs(t - s));

                    return r;
                }
            }

            this.PrecisionError = true;
            this.NeededIterations = iterations;
            this.RelativeError = Math.Abs(ExtendedMath.Fabs(t - s));

            return r;
        }
    }
}